package com.moyq5.mvc.framework.encrypt;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;

import javax.crypto.Cipher;

public class RSA {
	private static final String KEY_ALGORITHM = "RSA";
	private static final String MD5_ALGORITHM = "MD5WithRSA";
	private static final String SHA1_ALGORITHM = "SHA1WithRSA";
	private static final String SHA256_ALGORITHM = "SHA256WithRSA";
	private static final String DEFAULT_CHARSET = "UTF-8";
	
	public static byte[] signSHA1withRSA(byte[] data, byte[] privateKey) throws Exception {
		return sign(data, privateKey, SHA1_ALGORITHM);
	}
	
	public static String signSHA1withRSA2Hex(byte[] data, String privateKey) throws Exception {
		return HEX.toHexString(signSHA1withRSA(data, Base64.getDecoder().decode(privateKey)));
	}
	
	public static String signSHA1withRSA2Base64(byte[] data, String privateKey) throws Exception {
		return Base64.getEncoder().encodeToString(signSHA1withRSA(data, Base64.getDecoder().decode(privateKey)));
	}

	public static byte[] signSHA256withRSA(byte[] data, byte[] privateKey) throws Exception {
		return sign(data, privateKey, SHA256_ALGORITHM);
	}
	
	public static String signSHA256withRSA2Hex(byte[] data, String privateKey) throws Exception {
		return HEX.toHexString(signSHA256withRSA(data, Base64.getDecoder().decode(privateKey)));
	}
	
	public static String signSHA256withRSA2Base64(byte[] data, String privateKey) throws Exception {
		return Base64.getEncoder().encodeToString(signSHA256withRSA(data, Base64.getDecoder().decode(privateKey)));
	}
	
	public static String signSHA256withRSA2Base64(String data, String privateKey) throws Exception {
		return Base64.getEncoder().encodeToString(signSHA256withRSA(data.getBytes(DEFAULT_CHARSET), Base64.getDecoder().decode(privateKey)));
	}

	public static byte[] signMD5withRSA(byte[] data, byte[] privateKey) throws Exception {
		return sign(data, privateKey, MD5_ALGORITHM);
	}
	
	public static byte[] sign(byte[] data, byte[] privateKey, String algorithm) throws Exception {
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
		Signature signature = Signature.getInstance(algorithm);
		signature.initSign(priKey);
		signature.update(data);
		return signature.sign();
	}
	
	public static String signMD5withRSA2Hex(byte[] data, String privateKey) throws Exception {
		return signMD5withRSA2Hex(data, Base64.getDecoder().decode(privateKey));
	}
	
	public static String signMD5withRSA2Base64(byte[] data, String privateKey) throws Exception {
		return Base64.getEncoder().encodeToString(signMD5withRSA(data, Base64.getDecoder().decode(privateKey)));
	}
	
	public static String signMD5withRSA2Hex(byte[] data, byte[] privateKey) throws Exception {
		return HEX.toHexString(signMD5withRSA(data, privateKey));
	}
	
	public static boolean verifyMD5withRSA(byte[] data, byte[] sign, byte[] publicKey) throws Exception {
		return verify(data, sign, publicKey, MD5_ALGORITHM);
	}

	public static boolean verifyMD5withRSA(String data, String sign, String publicKey) throws Exception {
		return verifyMD5withRSA(data.getBytes(DEFAULT_CHARSET), sign.getBytes(DEFAULT_CHARSET), Base64.getDecoder().decode(publicKey));
	}
	
	public static boolean verifyMD5withRSA4Hex(String data, String sign, String publicKey) throws Exception {
		return verifyMD5withRSA(data.getBytes(DEFAULT_CHARSET), HEX.toByteArray(sign), Base64.getDecoder().decode(publicKey));
	}
	
	public static boolean verifyMD5withRSA4Base64(String data, String sign, String publicKey) throws Exception {
		return verifyMD5withRSA(data.getBytes(DEFAULT_CHARSET), Base64.getDecoder().decode(sign), Base64.getDecoder().decode(publicKey));
	}
	
	public static boolean verifySHA1WithRSA(byte[] data, byte[] sign, byte[] publicKey) throws Exception {
		return verify(data, sign, publicKey, SHA1_ALGORITHM);
	}
	
	public static boolean verifySHA1WithRSA(String data, String sign, String publicKey) throws Exception {
		return verifySHA1WithRSA(data.getBytes(DEFAULT_CHARSET), sign.getBytes(DEFAULT_CHARSET), Base64.getDecoder().decode(publicKey));
	}
	
	public static boolean verifySHA1WithRSA4Hex(String data, String sign, String publicKey) throws Exception {
		return verifySHA1WithRSA(data.getBytes(DEFAULT_CHARSET), HEX.toByteArray(sign), Base64.getDecoder().decode(publicKey));
	}
	
	public static boolean verifySHA1WithRSA4Base64(String data, String sign, String publicKey) throws Exception {
		return verifySHA1WithRSA(data.getBytes(DEFAULT_CHARSET), Base64.getDecoder().decode(sign), Base64.getDecoder().decode(publicKey));
	}
	
	public static boolean verify(byte[] data, byte[] sign, byte[] publicKey, String algorithm) throws Exception {
		/*
		InputStream stream = new ByteArrayInputStream(publicKey);
		Certificate cert = CertificateFactory.getInstance("X.509").generateCertificate(stream);
		PublicKey pubKey = cert.getPublicKey();
		*/
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey pubKey = keyFactory.generatePublic(keySpec);
		Signature signature = Signature.getInstance(algorithm);
		signature.initVerify(pubKey);
		signature.update(data);
		return signature.verify(sign);
	}
	
	public static String encrypt(String data, String publicKey) throws Exception{
		return Base64.getEncoder().encodeToString(encrypt(data.getBytes("UTF-8"), Base64.getDecoder().decode(publicKey)));
	}
	
	public static byte[] encrypt(byte[] data, byte[] publicKey) throws Exception{
		KeyFactory keyFactory =  KeyFactory.getInstance(KEY_ALGORITHM);
		PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
		Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
		cipher.init(Cipher.ENCRYPT_MODE, pubKey);
		int size = 117;
		int offSet = 0;
		byte[] result = {};
		byte[] cache = {};
		int len = data.length;
		while (len - offSet > 0) {
			if (len - offSet > size) {
				cache = cipher.doFinal(data, offSet, size);
				offSet += size;
			} else {
				cache = cipher.doFinal(data, offSet, len - offSet);
				offSet = len;
			}
			result = Arrays.copyOf(result, result.length + cache.length);
			System.arraycopy(cache, 0, result, result.length - cache.length, cache.length);
		}
		return result;
	}

	public static String decrypt(String data, String privateKey) throws Exception {  
		return new String(decrypt(Base64.getDecoder().decode(data), Base64.getDecoder().decode(privateKey)), "UTF-8");  
    }
	
	public static byte[] decrypt(byte[] data, byte[] privateKey) throws Exception {  
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);  
		PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec); 
		Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);  
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        int size = 128;
		int offSet = 0;
		byte[] result = {};
		byte[] cache = {};
		int len = data.length;
		while (len - offSet > 0) {
			if (len - offSet > size) {
				cache = cipher.doFinal(data, offSet, size);
				offSet += size;
			} else {
				cache = cipher.doFinal(data, offSet, len - offSet);
				offSet = len;
			}
			result = Arrays.copyOf(result, result.length + cache.length);
			System.arraycopy(cache, 0, result, result.length - cache.length, cache.length);
		}
        return result;  
    }

}
